package server;


import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.UnknownHostException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.util.Collections;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;

import common.JoinInfoException;
import common.JoinInfoMsg;
import common.RequestMsg;
import common.Sequencer;
import common.Server;
import common.UpdateMsg;



public class ServerImpl implements Server{

	private Sequencer sequencer;
	private String mySenderRef;

	private int sequenceNum;
	private Hashtable<Integer,Integer> storage;
	private int msgId;

	public ServerImpl(){

	}



	public void init(int regPort, String sequencerIpAddr){

		storage = new Hashtable<Integer,Integer>();
		msgId = 0;

		try {

			String myIpAddr = InetAddress.getLocalHost().getHostAddress();
			mySenderRef = myIpAddr+":"+String.valueOf(regPort);
			System.out.println(mySenderRef);
			sequencer = (Sequencer) Naming.lookup("rmi://"+sequencerIpAddr+":1099/sequencer");	
			JoinInfoMsg joinInfoMsg = sequencer.join(mySenderRef);

			if(joinInfoMsg!=null){
				sequenceNum = joinInfoMsg.getSequenceNum();
				storage = joinInfoMsg.getStorage();
				if(!storage.isEmpty()){
					System.out.println("Initial storage is:");
					System.out.println("dataId		value");
					List<Integer> tmp = Collections.list(storage.keys());
					Collections.sort(tmp);

					for(Integer key: tmp){
						System.out.printf("%d		%d\n",key,storage.get(key));
					}
				}
			} else 
				throw new JoinInfoException("Unable to get join information: joinInfoMsg is null\n");



		} catch (MalformedURLException | RemoteException | NotBoundException e) {
			e.printStackTrace();
		} catch (UnknownHostException | JoinInfoException e) {
			e.printStackTrace();
		}

	}






	@Override
	public Integer read(int dataId) throws RemoteException {

		return storage.get(Integer.valueOf(dataId));


	}



	@Override
	public void write(int dataId, int newValue) throws RemoteException {

		RequestMsg requestMsg = new RequestMsg(msgId++, dataId, newValue);
		System.out.printf("Server "+mySenderRef+" --> requestMsg sent with dataId=%d and newValue=%d\n", dataId,newValue);
		sequencer.send(mySenderRef, requestMsg);


	}




	private void writeUpdate(int dataId, int newValue){

		storage.put(Integer.valueOf(dataId), Integer.valueOf(newValue));
		System.out.printf("Server "+mySenderRef+" --> writeUpdate executed with dataId=%d and newValue=%d\n", dataId,newValue);
		System.out.println("Storage is:");
		System.out.println("dataId		value");
		//for(Integer key: storage.keySet()){
		//System.out.printf("%d		%d\n",key,storage.get(key));
		//}

		List<Integer> tmp = Collections.list(storage.keys());
		Collections.sort(tmp);

		for(Integer key: tmp){
			System.out.printf("%d		%d\n",key,storage.get(key));
		}
	}


	public void update(UpdateMsg updateMsg){

		if(updateMsg.getSequenceNum() > (sequenceNum + 1)){
			System.out.println("Calling askForMissing. Current seqNum is  "+ sequenceNum+", updateMsg with seqNum "+updateMsg.getSequenceNum()+" has been received.");
			askForMissing();
		} else {
			System.out.println("Current seqNum is  "+ sequenceNum+", updateMsg with seqNum "+updateMsg.getSequenceNum()+" has been received.");

			sequenceNum++;
			writeUpdate(updateMsg.getRequestMsg().getDataId(),updateMsg.getRequestMsg().getNewValue());
		}


	}

	private void askForMissing(){

		try {
			List<UpdateMsg> missings = sequencer.getMissing(mySenderRef, sequenceNum);
			for(UpdateMsg updateMsg: missings){
				sequenceNum = updateMsg.getSequenceNum();
				writeUpdate(updateMsg.getRequestMsg().getDataId(),updateMsg.getRequestMsg().getNewValue());
			}
		} catch (RemoteException e) {

			e.printStackTrace();
		}

	}


}